package dk.kapetanovic.jaft.log;


import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.List;

import dk.kapetanovic.jaft.action.Action;
import dk.kapetanovic.jaft.exception.InconsistentStateException;
import dk.kapetanovic.jaft.exception.TransactionException;

public class RecoveryJournal implements Journal {
	
	public static void dumpJournal(File journalFile) throws Exception {
		System.out.println("journalFile=" + journalFile);
		
		ArrayList<Action> acts = new ArrayList<Action>();
		RandomAccessFile raf = null;
		int s;
		try {
			raf = new RandomAccessFile(journalFile, "r");
			s = read(raf, acts, journalFile);
		}
		finally {
			if(raf != null)
				raf.close();
		}
		
		switch(s) {
			case OutputJournal.COMMITED: 
				System.out.println("COMMITED");
				break;
			case 0:
				System.out.println("All actions have been undone"); 
				return;
			default: 
				System.out.println("Actions to undo " + s + " of " + acts.size());
		}
		
		System.out.println("\n - - Actions");
		for(Action a : acts)
			System.out.println(a);
		System.out.println(" - - End\n");
	}
	
	private JournalImpl journal;
	private int undo;
	private File journalFile;
	
	public RecoveryJournal(File journalFile) throws IOException,
			ClassNotFoundException {
		this.journalFile = journalFile;
		ArrayList<Action> tmp = new ArrayList<Action>();
		ArrayList<Action> actions = new ArrayList<Action>();
		
		RandomAccessFile raf = null;
		try {
			raf = new RandomAccessFile(journalFile, "r");
			undo = read(raf, tmp, journalFile);
		}
		finally {
			if(raf != null)
				raf.close();
		}
		
		boolean noWork = (undo == OutputJournal.COMMITED) || (undo == 0);
		if(!noWork)
			for(int i = 0; i < undo; i++)
				actions.add(tmp.get(i));
		
		journal = new JournalImpl(actions);
	}

	@Override
	public void addAction(Action action) {}

	@Override
	public List<Action> commit() {
		return new ArrayList<Action>();
	}

	@Override
	public Action undo() throws InconsistentStateException {
		return journal.undo();
	}

	@Override
	public void close() throws TransactionException {
		journal.close();
		if(!journalFile.delete())
			throw new TransactionException("Could not delete journal file: " +
				journalFile.getAbsolutePath());
	}

	@Override
	public int size() {
		return journal.size();
	}

	private static int read(RandomAccessFile raf, 
		ArrayList<Action> actions, File journalFile) 
			throws IOException, ClassNotFoundException {
		//RandomAccessFile raf = journalFile;
		int undoCount = 0;
		int noCount = 0;
		
		raf.seek(0);
		long length = raf.length();
		long ptr = 0;
		
		outer:
		while((ptr = raf.getFilePointer()) < length) {
			int status = raf.readInt();
			
			switch(status) {
				case OutputJournal.UNDONE:
					if(ptr == 0)
						break outer;
					
					undoCount++;
					break;
				case OutputJournal.COMMITED:
					boolean end = (raf.getFilePointer() == length);
					if(end && undoCount == 0)
						return OutputJournal.COMMITED;
					else if(!end)
						throw getIOException("COMMITED encountered too early.", 
							journalFile);
					
					break;
				case OutputJournal.NO_STATE:
					if(undoCount > 0)
						throw getIOException("NO_STATE encountered after UNDONE.", 
							journalFile);
					
					noCount++;
					break;
				default:
					throw getIOException("Unknown state.", journalFile);
			}
			
			int len = raf.readInt();
			if(len < 0)
				throw getIOException("Length og buffer is negativ.", journalFile);
			
			byte[] buf = new byte[len];
			raf.read(buf, 0, len);
			
			ByteArrayInputStream bain = new ByteArrayInputStream(buf);
			ObjectInputStream oin = new ObjectInputStream(bain);
			bain.close();
			oin.close();
			Action a = (Action) oin.readObject();
			actions.add(a);
		}
		
		return noCount;
	}
	
	private static IOException getIOException(String msg, File journalFile) {
		return new IOException("Journal file corrupted: " + 
			journalFile.getAbsolutePath() + ". " + msg);
	}
}
